﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;

using LostLib.Win32;

namespace LostLib.Windows
{
	public sealed class KeyboardHook: IDisposable
	{
		/// <summary>
		/// Represents the window that is used internally to get the messages.
		/// </summary>
		private class Window: NativeWindow, IDisposable
		{
			private const int WM_HOTKEY = 0x0312;

			public Window( )
			{
				// create the handle for the window.
				this.CreateHandle(new CreateParams( ));
			}

			/// <summary>
			/// Overridden to get the notifications.
			/// </summary>
			/// <param name="m"></param>
			protected override void WndProc(ref Message m)
			{
				base.WndProc(ref m);

				// check if we got a hot key pressed.
				if (m.Msg == WM_HOTKEY)
				{
					// get the keys.
					Keys key = (Keys)(((int)m.LParam >> 16) & 0xFFFF);

					ModifierKeys modifier = (ModifierKeys)((int)m.LParam & 0xFFFF);
					if ((modifier & ModifierKeys.Alt) == ModifierKeys.Alt)
						key |= Keys.Alt;
					if ((modifier & ModifierKeys.Control) == ModifierKeys.Control)
						key |= Keys.Control;
					if ((modifier & ModifierKeys.Shift) == ModifierKeys.Shift)
						key |= Keys.Shift;
					if ((modifier & ModifierKeys.Win) == ModifierKeys.Win)
						key |= Keys.LWin;

					// invoke the event to notify the parent.
					if (KeyPressed != null)
						KeyPressed(this, new KeyEventArgs(key));
				}
			}

			public event KeyEventHandler KeyPressed;

			#region IDisposable Members
			private bool mDisposed = false;
			public void Dispose( )
			{
				if (mDisposed)
					return;

				mDisposed = true;
				this.DestroyHandle( );
			}
			~Window( )
			{
				Dispose( );
			}
			#endregion
		}

		private Window mWindow = new Window( );
		private int mCurrentId;
		private Dictionary<Keys, int> mHotkeys = new Dictionary<Keys, int>( );

		public KeyboardHook( )
		{
			// register the event of the inner native window.
			mWindow.KeyPressed += delegate(object sender, KeyEventArgs args)
			{
				if (KeyPressed != null)
					KeyPressed(this, args);
			};
		}

		/// <summary>
		/// Registers a hot key in the system.
		/// </summary>
		/// <param name="modifier">The modifiers that are associated with the hot key.</param>
		/// <param name="key">The key itself that is associated with the hot key.</param>
		public void RegisterHotKey(Keys key)
		{
			// increment the counter.
			mCurrentId = mCurrentId + 1;

			var modifier = ModifierKeys.None;

			if ((key & Keys.LWin) == Keys.LWin)
				modifier |= ModifierKeys.Win;
			if ((key & Keys.Shift) == Keys.Shift)
				modifier |= ModifierKeys.Shift;
			if ((key & Keys.Control) == Keys.Control)
				modifier |= ModifierKeys.Control;
			if ((key & Keys.Alt) == Keys.Alt)
				modifier |= ModifierKeys.Alt;

			// register the hot key.
			if (!WindowsAPI.RegisterHotKey(mWindow.Handle, mCurrentId, (uint)modifier, (uint)(key & Keys.KeyCode)))
				throw new InvalidOperationException("Couldn't register the hot key.");

			mHotkeys.Add(key, mCurrentId);
		}

		public void UnregisterHotKey(Keys key)
		{
			int id = 0;
			if (!mHotkeys.TryGetValue(key, out id))
				throw new InvalidOperationException("This key was not registered by this instance");

			if (!WindowsAPI.UnregisterHotKey(mWindow.Handle, id))
				throw new InvalidOperationException("Couldn't register the hot key.");

			mHotkeys.Remove(key);
		}

		/// <summary>
		/// A hot key has been pressed.
		/// </summary>
		public event KeyEventHandler KeyPressed;

		#region IDisposable Members
		private bool mDisposed = false;
		public void Dispose( )
		{
			if (mDisposed)
				return;

			mDisposed = true;
			// unregister all the registered hot keys.
			foreach (var id in mHotkeys.Values)
				WindowsAPI.UnregisterHotKey(mWindow.Handle, id);

			// dispose the inner native window.
			mWindow.Dispose( );
		}
		~KeyboardHook( )
		{
			Dispose( );
		}
		#endregion
	}

	/// <summary>
	/// The enumeration of possible modifiers.
	/// </summary>
	[Flags, Serializable]
	enum ModifierKeys: uint
	{
		None = 0,
		Alt = 1,
		Control = 2,
		Shift = 4,
		Win = 8
	}

#warning Code must be made portable
}
